package com.ezbuy.common.helper;

import com.ezbuy.common.bean.*;
import com.ezbuy.common.functions.Checker;
import com.ezbuy.common.functions.EmptyChecker;
import com.google.common.truth.Truth;
import com.google.protobuf.DescriptorProtos;

import org.junit.Test;

import java.io.File;
import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.List;

public class ProtoHelper {

    public static int fileCount = 0;
    public static int enumCount = 0;
    public static int messageCount = 0;//嵌套的静态内部类不算在内
    public static int serviceCount = 0;
    public static int nestedMessageCount = 0;

    //Protoc配置
    public static final String descDir = Config.projectPath + "/gen/desc";

    //FreeMaker配置
    public static final String ftlDir = "src/main/resources/ftl";
    public static final String outDir = "src/test/java/com/ezbuy/gen";//输出java文件的地址+包地址

    public static final String listImport = "java.util.List";
    public static final String bytesImport = "com.google.protobuf.ByteString";

    /**
     * proto -> desc
     */
    public static String build(String protoPath, String protoDir, String descDir) {
        String name = FileHelper.getFileNameWithOutPostfix(protoPath);
        String descPath = descDir + File.separator + name + ".desc";
        try {
            String cmd = String.format("protoc -I=%s --descriptor_set_out=%s %s", protoDir + "/.", descPath, protoPath);
            System.out.println(cmd);
            Runtime run = Runtime.getRuntime();
            Process p = run.exec(cmd);
            // 如果不正常终止, 则生成desc文件失败
            if (p.waitFor() != 0) {
                //p.exitValue()==0表示正常结束，1：非正常结束
                if (p.exitValue() == 1) {
                    throw new RuntimeException("命令执行失败");
                }
            }
            System.out.println("build complete:" + FileHelper.getFileNameWithPostfix(protoPath));
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("命令执行异常", e);
        }
        return descPath;
    }

    /**
     * desc -> bean
     */
    public static ProtoFile parse(String descPath) {
        ProtoFile protoFile = new ProtoFile();
        try {
            DescriptorProtos.FileDescriptorSet fdSet = DescriptorProtos.FileDescriptorSet.parseFrom(new FileInputStream(descPath));
            //可以将多个proto文件的描述信息生成到同一个desc文件，每个proto文件对应一个FileDescriptorProto对象
            //Proto
            for (DescriptorProtos.FileDescriptorProto file : fdSet.getFileList()) {
//                System.out.println(file);
                //这个rootPackageName是由输出目录决定的,可以在proto本来的package上再加上前缀
                String rootPackageName = NameHelper.getRootPackageName(outDir);
                String packageName = rootPackageName + "." + file.getPackage();
                protoFile.setName(file.getName());
                protoFile.setPackageName(packageName);
                protoFile.setGrpcPackageName(file.getPackage());
                //Message
                for (DescriptorProtos.DescriptorProto messageItem : file.getMessageTypeList()) {
                    parseMessage(protoFile, rootPackageName, messageItem);
                }
                //Enum
                for (DescriptorProtos.EnumDescriptorProto enumItem : file.getEnumTypeList()) {
                    parseEnum(protoFile, enumItem);
                }
                //Service
                for (DescriptorProtos.ServiceDescriptorProto serviceItem : file.getServiceList()) {
                    parseService(protoFile, rootPackageName, serviceItem);
                }
            }
        } catch (Exception e) {
            throw new RuntimeException("解析desc文件异常：" + descPath, e);
        }
        return protoFile;
    }

    static void parseMessage(ProtoFile protoFile, String rootPackageName, DescriptorProtos.DescriptorProto messageItem) {
        MessageType messageType = new MessageType();
        //Fileds
        for (DescriptorProtos.FieldDescriptorProto field : messageItem.getFieldList()) {
            FieldType fieldType = new FieldType();
            fieldType.setName(field.getName());
            String filedType = getFiledType(field);
            fieldType.setType(filedType);
            fieldType.setSingleType(getSingleTypeName(field));
            messageType.getFields().add(fieldType);
            //当存在字段有REPEATED属性时,添加依赖:java.util.List
            if (field.getLabel() == DescriptorProtos.FieldDescriptorProto.Label.LABEL_REPEATED) {
                messageType.addImport(listImport);
                fieldType.setListType(1);
            }
            //当字段名字是bytes,添加依赖:
            if (filedType.equals("ByteString")) {
                messageType.addImport(bytesImport);
                fieldType.setEntityType(1);
            }
            //当字段是自定义实体类时,添加该依赖
            if (field.getType() == DescriptorProtos.FieldDescriptorProto.Type.TYPE_MESSAGE || field.getType() == DescriptorProtos.FieldDescriptorProto.Type.TYPE_ENUM) {
                messageType.addImport(rootPackageName + "." + getFullRelateTypeName(field.getTypeName()));
                fieldType.setEntityType(1);
            }
        }
        messageType.setName(messageItem.getName());
        protoFile.addMessageType(messageType);
        //嵌套的实体类
        for (DescriptorProtos.DescriptorProto nestedType : messageItem.getNestedTypeList()) {
            parseNestedMessage(protoFile, rootPackageName, messageType, nestedType);
        }
    }

    static void parseNestedMessage(ProtoFile protoFile, String rootPackageName, MessageType parent, DescriptorProtos.DescriptorProto messageItem) {
        System.out.println("parseNestedMessage" + protoFile.getPackageName());
        MessageType messageType = new MessageType();
        //Fileds
        for (DescriptorProtos.FieldDescriptorProto field : messageItem.getFieldList()) {
            FieldType fieldType = new FieldType();
            fieldType.setName(field.getName());
            String filedType = getFiledType(field);
            fieldType.setType(filedType);
            messageType.getFields().add(fieldType);
            //当存在字段有REPEATED属性时,添加依赖:java.util.List
            if (field.getLabel() == DescriptorProtos.FieldDescriptorProto.Label.LABEL_REPEATED) {
                messageType.addImport(listImport);
            }
            //当字段名字是bytes,添加依赖:
            if (filedType.equals("ByteString")) {
                messageType.addImport(bytesImport);
            }
            //当字段是自定义实体类时,添加该依赖
            if (field.getType() == DescriptorProtos.FieldDescriptorProto.Type.TYPE_MESSAGE || field.getType() == DescriptorProtos.FieldDescriptorProto.Type.TYPE_ENUM) {
                messageType.addImport(rootPackageName + "." + getFullRelateTypeName(field.getTypeName()));
            }
        }
        messageType.setName(messageItem.getName());
        //嵌套的实体类
        for (DescriptorProtos.DescriptorProto nestedType : messageItem.getNestedTypeList()) {
            parseNestedMessage(protoFile, rootPackageName, messageType, nestedType);
        }
        parent.addNestedType(messageType);
    }

    static void parseService(ProtoFile protoFile, String rootPackageName, DescriptorProtos.ServiceDescriptorProto serviceItem) {
        ServiceType serviceType = new ServiceType();
        //Methods
        for (DescriptorProtos.MethodDescriptorProto methodItem : serviceItem.getMethodList()) {
            MethodType methodType = new MethodType();
            methodType.setName(NameHelper.bigHumpToSmallHump(methodItem.getName()));
            String inputType = getSafeName(getRelateTypeName(methodItem.getInputType()));
            String outputType = getSafeName(getRelateTypeName(methodItem.getOutputType()));
            methodType.setInputType(inputType);
            methodType.setInputName(NameHelper.bigHumpToSmallHump(inputType));
            methodType.setOutputType(outputType);
            serviceType.addMethod(methodType);
            //添加入参和出参的依赖
//            if (!inputType.equals("Empty")) {
            serviceType.addImport(rootPackageName + "." + getFullRelateTypeName(methodItem.getInputType()));
//            }
//            if (!inputType.equals("Empty")) {
            serviceType.addImport(rootPackageName + "." + getFullRelateTypeName(methodItem.getOutputType()));
//            }
        }
        serviceType.setName(serviceItem.getName());
        protoFile.addSeiviceType(serviceType);
    }

    static void parseEnum(ProtoFile protoFile, DescriptorProtos.EnumDescriptorProto enumItem) {
        EnumType enumType = new EnumType();
        //Values
        for (DescriptorProtos.EnumValueDescriptorProto value : enumItem.getValueList()) {
            enumType.addValue(value.getName());
        }
        enumType.setName(enumItem.getName());
        protoFile.addEnumType(enumType);
    }

    /**
     * proto -> File
     */
    public static void protoToFile(String protoPath, String protoDir) {
        String descPath = ProtoHelper.build(protoPath, protoDir, ProtoHelper.descDir);
        ProtoHelper.descToFile(descPath);
    }

    /**
     * bean -> java List
     */
    public static void descToFile(String descPath) {
        ProtoFile protoFile = parse(descPath);
        String packageName = protoFile.getPackageName();
        String outDir = NameHelper.getRootWithoutPackageName(ProtoHelper.outDir);
        String realPackageName = NameHelper.replaceToSeparator(packageName);

        //创建GrpcBean
//        GrpcBean grpcBean = new GrpcBean(protoFile);
//        FreeMakerHelper.generate(grpcBean, realPackageName, grpcBean.getName(), ".java", "GrpcBean", ftlDir, outDir);

        boolean isOutClass = false;
        for (ServiceType serviceType : protoFile.getServiceTypes()) {
            //创建最原始的同步的接口类
            FreeMakerHelper.generate(serviceType, realPackageName, serviceType.getName(), ".java", "Service", ftlDir, outDir);
            //创建异步的接口类
            FreeMakerHelper.generate(serviceType, realPackageName, serviceType.getName() + "Service", ".java", "WebService", ftlDir, outDir);
            //创建GRPC的实现类
//            FreeMakerHelper.generate(serviceType, realPackageName, serviceType.getName()+"Grpc", ".java", "ServiceGrpc", ftlDir, outDir);
            //创建异步接口的实现类
            FreeMakerHelper.generate(serviceType, realPackageName, serviceType.getName() + "ServiceImpl", ".java", "WebServiceImpl", ftlDir, outDir);
            //创建测试类
            FreeMakerHelper.generate(serviceType, realPackageName, serviceType.getName() + "ServiceTest", ".java", "WebServiceTest", ftlDir, outDir);
            fileCount++;
            serviceCount++;
            isOutClass = true;
        }
        for (MessageType messageType : protoFile.getMessageTypes()) {
            fileCount++;
            messageCount++;
            if (isOutClass) {
                //创建Message
                FreeMakerHelper.generate(messageType, realPackageName, messageType.getName(), ".java", "Message", ftlDir, outDir);
                //创建MessageMapper
                FreeMakerHelper.generate(messageType, realPackageName, messageType.getName() + "Mapper", ".java", "MessageMapper", ftlDir, outDir);
            } else {
                //创建Message
                FreeMakerHelper.generate(messageType, realPackageName, messageType.getName(), ".java", "Message", ftlDir, outDir);
                //创建MessageMapper
                FreeMakerHelper.generate(messageType, realPackageName, messageType.getName() + "Mapper", ".java", "InnerMessageMapper", ftlDir, outDir);
            }
        }
        for (EnumType enumType : protoFile.getEnumTypes()) {
            fileCount++;
            enumCount++;
            if (isOutClass) {
                //创建Enum
                FreeMakerHelper.generate(enumType, realPackageName, enumType.getName(), ".java", "Enum", ftlDir, outDir);
                //创建EnumMapper
                FreeMakerHelper.generate(enumType, realPackageName, enumType.getName() + "Mapper", ".java", "EnumMapper", ftlDir, outDir);
            } else {
                //创建Enum
                FreeMakerHelper.generate(enumType, realPackageName, enumType.getName(), ".java", "Enum", ftlDir, outDir);
                //创建EnumMapper
                FreeMakerHelper.generate(enumType, realPackageName, enumType.getName() + "Mapper", ".java", "InnerEnumMapper", ftlDir, outDir);
            }
        }

    }

    /**
     * proto dir -> java list
     */
    public static void generateFromProtoDir(String protoDir) {
        generateFromProtoDir(protoDir, new EmptyChecker());
    }

    public static void generateFromProtoDir(String protoDir, Checker<String> filter) {
        //get proto list
        List<String> list = new ArrayList<>();
        FileHelper.traverseFolder(list, protoDir, filter);
        System.out.println("proto文件数量:" + list.size());
        //proto -> file
        for (String protoPath : list) {
            ProtoHelper.protoToFile(protoPath, protoDir);
        }
    }

    public static void generateFromProtoDir(String protoDir, Checker<String> filter, List<String> whiteList, List<String> blackList) {
        //get proto list
        List<String> list = new ArrayList<>();
        FileHelper.traverseFolder(list, protoDir, filter);
        list.addAll(whiteList);
        list.removeAll(blackList);
        System.out.println("proto文件数量:" + list.size());
        //proto -> file
        for (String protoPath : list) {
            ProtoHelper.protoToFile(protoPath, protoDir);
        }
    }

    /**
     * Type -String
     */
    private static String getFiledType(DescriptorProtos.FieldDescriptorProto field) {
        DescriptorProtos.FieldDescriptorProto.Label label = field.getLabel();
        String name = getSingleTypeName(field);
        //列表
        if (label == DescriptorProtos.FieldDescriptorProto.Label.LABEL_REPEATED) {
            return String.format("List<%s>", getSafeName(name));
        }
        return name;
    }

    private static String getSafeName(String name) {
        switch (name) {
            case "boolean":
            case "Boolean":
                return "Boolean";
            case "int":
            case "Int":
                return "Integer";
            case "long":
            case "Long":
                return "Long";
            case "float":
            case "Float":
                return "Float";
            case "double":
            case "Double":
                return "Double";
        }
        return name;
    }

    private static String getSingleTypeName(DescriptorProtos.FieldDescriptorProto field) {
        DescriptorProtos.FieldDescriptorProto.Type type = field.getType();
        switch (type) {
            case TYPE_BOOL:
                return "boolean";
            case TYPE_INT32:
            case TYPE_UINT32:
            case TYPE_SINT32:
            case TYPE_FIXED32:
            case TYPE_SFIXED32:
                return "int";
            case TYPE_INT64:
            case TYPE_UINT64:
            case TYPE_SINT64:
            case TYPE_FIXED64:
            case TYPE_SFIXED64:
                return "long";
            case TYPE_FLOAT:
                return "float";
            case TYPE_DOUBLE:
                return "double";
            case TYPE_STRING:
                return "String";
            // TODO: 2018/12/1 这个数据类型好像有问题,需要研究下
            case TYPE_BYTES:
                return "ByteString";
            case TYPE_ENUM:
                return getRelateTypeName(field.getTypeName());
            case TYPE_MESSAGE:
                return getRelateTypeName(field.getTypeName());
            default:
        }
        return "String";
    }

    /**
     * .address.Person -> Person
     * 获取依赖的类的简单名字
     */
    public static String getRelateTypeName(String typeName) {
        String[] split = typeName.split("\\.");
        return split[split.length - 1];
    }

    /**
     * .address.Person -> address.Person
     * 获取依赖的类的全部名字
     */
    public static String getFullRelateTypeName(String typeName) {
        return typeName.substring(1);
    }

    @Test
    public void testGetRelateTypeName() {
        String typeName = ".com.example.tutorial.PhoneNumber";
        String name = ProtoHelper.getRelateTypeName(typeName);
        Truth.assertThat(name).isEqualTo("PhoneNumber");
    }

    @Test
    public void testGetFullRelateTypeName() {
        String typeName = ".com.example.tutorial.PhoneNumber";
        String name = ProtoHelper.getFullRelateTypeName(typeName);
        Truth.assertThat(name).isEqualTo("com.example.tutorial.PhoneNumber");
    }
}
